"""
微信公众号API集成服务
"""
import httpx
import json
import time
from datetime import datetime, timedelta
from typing import Dict, Any, Optional, List
from app.config import settings, app_config
from app.utils.logger import logger
from app.utils.exceptions import WeChatAPIError


class AccessTokenManager:
    """AccessToken管理器"""
    
    def __init__(self):
        self.app_id = settings.wechat_app_id
        self.app_secret = settings.wechat_app_secret
        self.base_url = settings.wechat_base_url
        self.token = None
        self.expires_at = None
    
    async def get_access_token(self) -> str:
        """获取有效的AccessToken"""
        if self.token and self.expires_at and datetime.now() < self.expires_at:
            return self.token
        
        await self._refresh_token()
        return self.token
    
    async def _refresh_token(self):
        """刷新AccessToken"""
        url = f"{self.base_url}/cgi-bin/token"
        params = {
            "grant_type": "client_credential",
            "appid": self.app_id,
            "secret": self.app_secret
        }
        
        try:
            async with httpx.AsyncClient(timeout=30.0) as client:
                response = await client.get(url, params=params)
                response.raise_for_status()
                
                result = response.json()
                
                if "access_token" in result:
                    self.token = result["access_token"]
                    expires_in = result.get("expires_in", 7200)
                    # 提前10分钟过期，确保token有效性
                    self.expires_at = datetime.now() + timedelta(seconds=expires_in - 600)
                    logger.info(f"AccessToken刷新成功，过期时间: {self.expires_at}")
                else:
                    error_msg = result.get("errmsg", "未知错误")
                    raise WeChatAPIError(f"获取AccessToken失败: {error_msg}")
                    
        except Exception as e:
            logger.error(f"刷新AccessToken失败: {str(e)}")
            raise WeChatAPIError(f"刷新AccessToken失败: {e}")


class WeChatService:
    """微信公众号服务类"""
    
    def __init__(self):
        self.token_manager = AccessTokenManager()
        self.config = app_config.wechat
        self.base_url = settings.wechat_base_url
    
    async def _make_request(self, url: str, method: str = "GET", data: Dict = None, files: Dict = None) -> Dict:
        """发送微信API请求"""
        access_token = await self.token_manager.get_access_token()
        
        if "?" in url:
            full_url = f"{url}&access_token={access_token}"
        else:
            full_url = f"{url}?access_token={access_token}"
        
        retry_count = 0
        max_retries = self.config["upload_retry_times"]
        
        while retry_count < max_retries:
            try:
                # 设置请求头，确保UTF-8编码
                headers = {
                    "Content-Type": "application/json; charset=UTF-8"
                }
                
                async with httpx.AsyncClient(timeout=60.0) as client:
                    if method.upper() == "GET":
                        response = await client.get(full_url)
                    elif method.upper() == "POST":
                        if files:
                            # 文件上传时使用multipart/form-data，不设置Content-Type（让httpx自动设置）
                            # 确保files和data都正确传递
                            logger.debug(f"上传文件，files keys: {list(files.keys())}, data keys: {list(data.keys()) if data else []}")
                            response = await client.post(full_url, files=files, data=data or {})
                        else:
                            # JSON请求时确保使用UTF-8编码，中文字符不被转义
                            # 使用json.dumps确保ensure_ascii=False，这样中文字符不会被转义为\uXXXX格式
                            json_str = json.dumps(data, ensure_ascii=False, separators=(',', ':'))
                            json_bytes = json_str.encode('utf-8')
                            
                            # 使用content参数直接发送UTF-8编码的字节
                            response = await client.post(
                                full_url,
                                content=json_bytes,
                                headers={"Content-Type": "application/json; charset=UTF-8"}
                            )
                    else:
                        raise ValueError(f"不支持的HTTP方法: {method}")
                    
                    response.raise_for_status()
                    result = response.json()
                    
                    # 检查微信API错误码
                    if result.get("errcode", 0) != 0:
                        error_msg = result.get("errmsg", "未知错误")
                        error_code = result.get("errcode")
                        
                        # Token过期，重新获取
                        if error_code in [40001, 40014, 42001]:
                            logger.warning("AccessToken过期，重新获取")
                            await self.token_manager._refresh_token()
                            retry_count += 1
                            continue
                        else:
                            raise WeChatAPIError(f"微信API错误 {error_code}: {error_msg}")
                    
                    return result
                    
            except httpx.HTTPStatusError as e:
                logger.error(f"微信API HTTP错误: {e.response.status_code} - {e.response.text}")
                retry_count += 1
                if retry_count >= max_retries:
                    raise WeChatAPIError(f"微信API调用失败: {e}")
            except Exception as e:
                logger.error(f"微信API调用异常: {str(e)}")
                retry_count += 1
                if retry_count >= max_retries:
                    raise WeChatAPIError(f"微信API调用异常: {e}")
        
        raise WeChatAPIError("微信API调用重试次数超限")
    
    async def upload_image(self, image_data: bytes, filename: str = "image.jpg", mime_type: str = "image/jpeg") -> Dict[str, str]:
        """上传图片素材，返回media_id和url"""
        url = f"{self.base_url}/cgi-bin/material/add_material"
        
        # 验证图片数据
        if not image_data or len(image_data) == 0:
            logger.error(f"图片数据为空，无法上传")
            raise WeChatAPIError("图片数据为空，无法上传")
        
        # 验证图片大小（微信限制图片大小不超过2MB）
        max_size = 2 * 1024 * 1024  # 2MB
        if len(image_data) > max_size:
            logger.error(f"图片大小({len(image_data)}字节)超过微信限制({max_size}字节)")
            raise WeChatAPIError(f"图片大小超过微信限制(2MB)")
        
        # 确保文件名为有效的字符串
        if isinstance(filename, bytes):
            filename = filename.decode('utf-8', errors='ignore')
        
        files = {
            "media": (filename, image_data, mime_type)
        }
        
        data = {
            "type": "image"
        }
        
        logger.info(f"开始上传图片素材: {filename}, 大小: {len(image_data)} 字节, MIME类型: {mime_type}")
        result = await self._make_request(url, method="POST", data=data, files=files)
        
        media_id = result.get("media_id")
        if not media_id:
            raise WeChatAPIError("上传图片失败，未返回media_id")
        
        logger.info(f"图片上传成功，media_id: {media_id}")
        
        # 微信add_material接口不返回CDN URL，需要通过get_material获取
        # 或者直接使用media_id，微信富文本编辑器会自动处理
        cdn_url = result.get("url", "")
        if not cdn_url:
            logger.info(f"微信API未返回URL，使用media_id: {media_id}")
            # 微信富文本编辑器可以直接使用media_id
            cdn_url = media_id
        else:
            logger.info(f"微信API返回URL: {cdn_url}")
        
        return {
            "media_id": media_id,
            "url": cdn_url  # 返回URL或media_id
        }
    
    async def upload_image_from_url(self, image_url: str) -> Dict[str, str]:
        """从URL上传图片，返回包含media_id和url的字典"""
        try:
            logger.info(f"开始从URL下载图片: {image_url}")
            
            # 下载图片
            async with httpx.AsyncClient(timeout=60.0, follow_redirects=True) as client:
                response = await client.get(image_url)
                response.raise_for_status()
                image_data = response.content
            
            # 验证图片数据
            if not image_data or len(image_data) == 0:
                logger.error(f"下载的图片数据为空，URL: {image_url}")
                raise WeChatAPIError("下载的图片数据为空")
            
            logger.info(f"图片下载成功，大小: {len(image_data)} 字节")
            
            # 根据Content-Type或文件头确定文件类型
            content_type = response.headers.get("Content-Type", "image/jpeg")
            if "png" in content_type.lower():
                file_ext = "png"
                mime_type = "image/png"
            elif "jpeg" in content_type.lower() or "jpg" in content_type.lower():
                file_ext = "jpg"
                mime_type = "image/jpeg"
            else:
                # 检查文件头
                if image_data.startswith(b'\x89PNG'):
                    file_ext = "png"
                    mime_type = "image/png"
                elif image_data.startswith(b'\xff\xd8\xff'):
                    file_ext = "jpg"
                    mime_type = "image/jpeg"
                else:
                    file_ext = "jpg"
                    mime_type = "image/jpeg"
            
            # 上传到微信
            filename = f"generated_image_{int(time.time())}.{file_ext}"
            logger.info(f"准备上传图片到微信，文件名: {filename}, MIME类型: {mime_type}, 大小: {len(image_data)} 字节")
            
            result = await self.upload_image(image_data, filename, mime_type)
            return result  # 返回完整的字典，包含media_id和url
            
        except httpx.HTTPStatusError as e:
            logger.error(f"下载图片HTTP错误: {e.response.status_code} - {e.response.text[:200]}")
            raise WeChatAPIError(f"下载图片失败: HTTP {e.response.status_code}")
        except httpx.TimeoutException as e:
            logger.error(f"下载图片超时: {str(e)}")
            raise WeChatAPIError(f"下载图片超时: {e}")
        except Exception as e:
            logger.error(f"从URL上传图片失败: {str(e)}", exc_info=True)
            raise WeChatAPIError(f"从URL上传图片失败: {e}")
    
    async def create_draft(self, articles: List[Dict[str, Any]]) -> str:
        """创建草稿"""
        url = f"{self.base_url}/cgi-bin/draft/add"
        
        # 在发送前再次验证每个文章的摘要长度（按字节计算）
        for i, article in enumerate(articles):
            logger.info(f"验证第{i+1}篇文章数据:")
            logger.info(f"  标题: {article.get('title', '')[:50]}...")
            
            # 处理摘要字段 - 确保摘要不超过100字符和120字节
            if 'digest' in article:
                digest = article.get('digest') or ""
                if digest:
                    # 先按字符截断到100字符
                    max_chars = 100
                    if len(digest) > max_chars:
                        logger.warning(f"  摘要字符长度({len(digest)})超过限制({max_chars}字符)，截断")
                        digest = digest[:max_chars]
                    
                    # 再检查字节长度（确保不超过120字节）
                    digest_bytes = digest.encode('utf-8')
                    max_bytes = 120
                    if len(digest_bytes) > max_bytes:
                        logger.warning(f"  摘要字节长度({len(digest_bytes)})超过限制({max_bytes}字节)，按字节截断")
                        # 按字节截断
                        safe_length = max_bytes
                        truncated_bytes = digest_bytes[:safe_length]
                        while len(truncated_bytes) > 0:
                            try:
                                truncated_digest = truncated_bytes.decode('utf-8')
                                break
                            except UnicodeDecodeError:
                                truncated_bytes = truncated_bytes[:-1]
                        digest = truncated_digest
                        logger.info(f"  摘要已按字节截断: 字符={len(digest)}, 字节={len(digest.encode('utf-8'))}")
                    
                    article['digest'] = digest
                else:
                    article['digest'] = ""
            else:
                article['digest'] = ""
            
            logger.info(f"  摘要最终长度: 字符={len(article.get('digest', ''))}, 字节={len(article.get('digest', '').encode('utf-8')) if article.get('digest') else 0}")
            
            # 验证标题长度（微信限制标题，根据测试可能需要更严格）
            if 'title' in article:
                title = article['title'] or ""
                if title:
                    title_bytes = title.encode('utf-8')
                    # 微信标题限制可能是64字节，但为了安全设置为更小的值
                    max_title_bytes = 60  # 设置为60字节，留出安全余量
                    if len(title_bytes) > max_title_bytes:
                        logger.warning(f"  标题字节长度({len(title_bytes)})超过微信限制({max_title_bytes}字节)，截断")
                        # 安全截断标题
                        safe_length = max_title_bytes - 3
                        truncated_bytes = title_bytes[:safe_length]
                        while len(truncated_bytes) > 0:
                            try:
                                truncated_title = truncated_bytes.decode('utf-8')
                                break
                            except UnicodeDecodeError:
                                truncated_bytes = truncated_bytes[:-1]
                        article['title'] = truncated_title + "..."
                        logger.info(f"  标题已截断: 字符={len(article['title'])}, 字节={len(article['title'].encode('utf-8'))}")
            
            # 再次验证标题长度（确保不超过限制）
            if 'title' in article:
                title = article['title'] or ""
                if title:
                    title_bytes = title.encode('utf-8')
                    max_title_bytes = 60  # 微信标题限制60字节
                    if len(title_bytes) > max_title_bytes:
                        logger.warning(f"  最终验证：标题字节长度({len(title_bytes)})超过限制({max_title_bytes}字节)，强制截断")
                        # 强制截断到安全长度
                        safe_length = max_title_bytes - 3
                        truncated_bytes = title_bytes[:safe_length]
                        while len(truncated_bytes) > 0:
                            try:
                                truncated_title = truncated_bytes.decode('utf-8')
                                break
                            except UnicodeDecodeError:
                                truncated_bytes = truncated_bytes[:-1]
                        article['title'] = truncated_title + "..."
                        logger.info(f"  标题已强制截断: 字符={len(article['title'])}, 字节={len(article['title'].encode('utf-8'))}")
            
            # 记录所有字段的长度（用于调试）
            logger.info(f"  文章数据字段长度检查:")
            for key, value in article.items():
                if isinstance(value, str):
                    byte_len = len(value.encode('utf-8'))
                    logger.info(f"    {key}: 字符={len(value)}, 字节={byte_len}")
                    # 如果标题或摘要超过限制，记录警告
                    if key == 'title' and byte_len > 60:
                        logger.error(f"    ⚠️ 标题字节长度({byte_len})仍然超过60字节限制！")
                    elif key == 'digest' and byte_len > 0:
                        logger.warning(f"    ⚠️ 摘要字节长度({byte_len})，应该为空字符串")
                else:
                    logger.info(f"    {key}: {type(value).__name__} = {value}")
        
        # 确保所有字符串字段都是UTF-8编码的字符串
        for article in articles:
            for key, value in article.items():
                if isinstance(value, str):
                    # 确保字符串是UTF-8编码
                    if isinstance(value, bytes):
                        article[key] = value.decode('utf-8', errors='ignore')
                    else:
                        # 确保字符串是有效的UTF-8
                        try:
                            article[key].encode('utf-8')
                        except UnicodeEncodeError:
                            # 如果编码失败，尝试修复
                            article[key] = value.encode('utf-8', errors='ignore').decode('utf-8', errors='ignore')
        
        data = {
            "articles": articles
        }
        
        logger.info(f"创建草稿，文章数量: {len(articles)}")
        result = await self._make_request(url, method="POST", data=data)
        
        media_id = result.get("media_id")
        if not media_id:
            raise WeChatAPIError("创建草稿失败，未返回media_id")
        
        logger.info(f"草稿创建成功，media_id: {media_id}")
        return media_id
    
    async def get_draft_list(self, offset: int = 0, count: int = 20) -> List[Dict]:
        """获取草稿列表"""
        url = f"{self.base_url}/cgi-bin/draft/batchget"
        
        data = {
            "offset": offset,
            "count": count
        }
        
        result = await self._make_request(url, method="POST", data=data)
        return result.get("item", [])
    
    async def delete_draft(self, media_id: str) -> bool:
        """删除草稿"""
        url = f"{self.base_url}/cgi-bin/draft/delete"
        
        data = {
            "media_id": media_id
        }
        
        try:
            await self._make_request(url, method="POST", data=data)
            logger.info(f"草稿删除成功: {media_id}")
            return True
        except Exception as e:
            logger.error(f"删除草稿失败: {str(e)}")
            return False
    
    async def get_material_url(self, media_id: str) -> str:
        """
        获取素材的CDN URL
        
        注意：此方法主要用于兼容性。实际应该使用upload_image返回的CDN URL。
        微信的图片显示需要使用mmbiz.qpic.cn域名的CDN地址。
        """
        logger.info(f"get_material_url方法返回media_id: {media_id}")
        logger.warning("建议直接使用upload_image返回的CDN URL，而不是通过此方法获取")
        return media_id

    async def get_material_list(self, material_type: str = "image", offset: int = 0, count: int = 20) -> List[Dict]:
        """获取素材列表"""
        url = f"{self.base_url}/cgi-bin/material/batchget_material"
        
        data = {
            "type": material_type,
            "offset": offset,
            "count": count
        }
        
        result = await self._make_request(url, method="POST", data=data)
        return result.get("item", [])
    
    def format_article_for_draft(self, title: str, content: str, summary: str, 
                                cover_media_id: str, author: str = "", 
                                content_source_url: str = "") -> Dict[str, Any]:
        """格式化文章数据用于创建草稿"""
        # 微信API对摘要(digest)的限制是120字节
        # 为了安全，限制摘要为100字符（约300字节，但实际中文字符每个3字节，100字符约300字节会超过）
        # 所以限制为100字符，确保字节长度不超过120
        max_digest_chars = 100  # 限制为100字符
        
        # 确保摘要不为None
        digest = summary or "" if summary else ""
        
        # 如果摘要超过100字符，截断
        if digest and len(digest) > max_digest_chars:
            logger.warning(f"摘要长度({len(digest)}字符)超过限制({max_digest_chars}字符)，截断")
            digest = digest[:max_digest_chars]
        
        # 验证字节长度（确保不超过120字节）
        if digest:
            digest_bytes = digest.encode('utf-8')
            max_bytes = 120
            if len(digest_bytes) > max_bytes:
                logger.warning(f"摘要字节长度({len(digest_bytes)})超过微信限制({max_bytes}字节)，按字节截断")
                # 按字节截断，确保不超过120字节
                safe_length = max_bytes
                truncated_bytes = digest_bytes[:safe_length]
                # 尝试解码，如果失败则继续缩短
                while len(truncated_bytes) > 0:
                    try:
                        truncated_digest = truncated_bytes.decode('utf-8')
                        break
                    except UnicodeDecodeError:
                        truncated_bytes = truncated_bytes[:-1]
                digest = truncated_digest
                logger.info(f"摘要已按字节截断: 字符={len(digest)}, 字节={len(digest.encode('utf-8'))}")
        
        # 记录最终摘要信息
        final_byte_len = len(digest.encode('utf-8')) if digest else 0
        logger.info(f"文章摘要最终长度: 字符={len(digest)}, 字节={final_byte_len}")
        
        # 验证标题长度（微信限制标题，根据测试可能需要更严格）
        final_title = title or ""
        if final_title:
            title_bytes = final_title.encode('utf-8')
            # 微信标题限制可能是64字节，但为了安全设置为更小的值
            max_title_bytes = 60  # 设置为60字节，留出安全余量
            if len(title_bytes) > max_title_bytes:
                logger.warning(f"标题字节长度({len(title_bytes)})超过微信限制({max_title_bytes}字节)，截断")
                # 安全截断标题
                safe_length = max_title_bytes - 3
                truncated_bytes = title_bytes[:safe_length]
                while len(truncated_bytes) > 0:
                    try:
                        truncated_title = truncated_bytes.decode('utf-8')
                        break
                    except UnicodeDecodeError:
                        truncated_bytes = truncated_bytes[:-1]
                final_title = truncated_title + "..."
                logger.info(f"标题已截断: 字符={len(final_title)}, 字节={len(final_title.encode('utf-8'))}")
        
        return {
            "title": final_title,
            "author": author,
            "digest": digest,
            "content": content,
            "content_source_url": content_source_url,
            "thumb_media_id": cover_media_id,
            "show_cover_pic": 1,  # 显示封面图
            "need_open_comment": 0,  # 不开启评论
            "only_fans_can_comment": 0  # 所有人可评论
        }
    
    async def test_connection(self) -> bool:
        """测试微信API连接"""
        try:
            access_token = await self.token_manager.get_access_token()
            logger.info("微信API连接测试成功")
            return True
        except Exception as e:
            logger.error(f"微信API连接测试失败: {str(e)}")
            return False


# 全局服务实例
wechat_service = WeChatService()